contents

자바에서 Comparator 는 특정 클래스의 객체에 대한 사용자 정의 순서를 지정하는 데 사용되는 강력한 함수형 인터페이스입니다. 이는 클래스 자체를 수정할 수 없거나 여러 가지 다른 정렬 규칙이 필요할 때, 정렬 로직을 외부에서 별도로 제어할 수 있는 필수적인 방법을 제공합니다.

Collections.sort()List.sort()와 같은 정렬 메서드에 두 객체를 어떻게 비교해야 하는지 정확히 알려주는 맞춤형 지침서를 제공하는 것으로 생각할 수 있습니다.


Comparator가 해결하는 문제

어떤 객체들은 이미 스스로 정렬하는 방법을 알고 있는데 왜 Comparator가 필요할까요? Comparator는 주로 세 가지 시나리오에서 사용됩니다.

  1. 서드파티 클래스 정렬: 소유하고 있지 않은 클래스(예: 라이브러리에서 가져온 클래스)의 객체를 정렬하고 싶을 때. 이 클래스를 수정하여 정렬 로직을 추가할 수 없으므로, 로직을 외부에서 제공해야 합니다.
  2. 다중 정렬 순서: Person 클래스가 있고, 사람 목록을 때로는 이름의 알파벳순으로, 때로는 나이순으로, 때로는 도시순으로 다르게 정렬해야 할 때. Comparator를 사용하면 각 경우에 대한 별도의 정렬 전략을 만들 수 있습니다.
  3. "자연스러운" 순서가 없는 클래스 정렬: 일부 클래스는 명확한 "자연스러운" 순서가 없습니다. Comparator를 사용하면 특정 상황에 맞는 의미 있는 순서를 정의할 수 있습니다.

Comparator vs. Comparable ⚖️

이것은 이해해야 할 가장 중요한 차이점입니다. 두 인터페이스 모두 정렬을 위한 것이지만, 목적이 다릅니다.

특징 Comparable Comparator
목적 클래스의 자연스러운, 기본 순서를 정의. 사용자 정의, 외부 정렬 로직을 정의. 한 클래스에 대해 여러 개의 다른 비교자를 가질 수 있음.
구현 클래스 자체가 이 인터페이스를 구현해야 함. 별도의 클래스가 이 인터페이스를 구현함.
메서드 public int compareTo(T other) public int compare(T o1, T o2)
패키지 java.lang java.util
비유 Person 객체는 자신의 나이를 알고 있음. 이것이 "자연스러운" 비교. 외부의 "판사"는 사람들을 키순으로, 그 다음엔 이름순으로 줄을 세울 수 있음. 판사가 비교 로직을 제공.

요컨대, 클래스를 정렬하는 가장 명백한 단 하나의 방법에는 Comparable을 사용하고, 그 외 모든 경우에는 Comparator를 사용하세요.


Comparator 구현 방법

Comparator를 만드는 방법에는 고전적인 접근 방식부터 자바 8에서 도입된 현대적이고 간결한 방법까지 여러 가지가 있습니다.

예제를 위해 간단한 Player 클래스를 사용하겠습니다.

class Player {
    private String name;
    private int score;
    // ... 생성자, getter, toString ...
}

1. 고전적인 방식: 별도의 클래스

Comparator 인터페이스를 구현하는 독립적인 클래스를 만들 수 있습니다.

class SortByScore implements Comparator {
    @Override
    public int compare(Player p1, Player p2) {
        // 오름차순 정렬
        return Integer.compare(p1.getScore(), p2.getScore());
    }
}

// 사용법:
// players.sort(new SortByScore());

2. 현대적인 방식: 람다 표현식 (자바 8부터)

Comparator는 함수형 인터페이스이므로(추상 메서드가 compare 하나뿐임), 람다 표현식을 사용할 수 있습니다. 훨씬 더 간결합니다.

// 점수 오름차순 정렬
Comparator sortByScore = (p1, p2) -> Integer.compare(p1.getScore(), p2.getScore());

// 이름 알파벳순 정렬
Comparator sortByName = (p1, p2) -> p1.getName().compareTo(p2.getName());

// 사용법:
// players.sort(sortByScore);

3. 최고의 방식: Comparator 정적 헬퍼 메서드 (자바 8부터)

자바 8은 강력한 정적 헬퍼 메서드를 추가하여 Comparator를 훨씬 더 쉽게 사용할 수 있게 만들었습니다. 이것이 가장 가독성이 좋고 권장되는 접근 방식입니다.

// 이름으로 정렬
Comparator sortByName = Comparator.comparing(Player::getName);

// 점수로 정렬
Comparator sortByScore = Comparator.comparingInt(Player::getScore); // int에 최적화됨

// 사용법:
// players.sort(Comparator.comparing(Player::getName));
Comparator sortByScoreThenName = Comparator
    .comparingInt(Player::getScore)
    .thenComparing(Player::getName);

// 사용법:
// players.sort(sortByScoreThenName);
// 점수 내림차순 정렬
Comparator sortByScoreDesc = Comparator.comparingInt(Player::getScore).reversed();

// 사용법:
// players.sort(sortByScoreDesc);

compare 메서드 계약 📜

모든 Comparator의 핵심은 compare(T o1, T o2) 메서드입니다. 이 메서드는 엄격한 계약을 따라야 합니다.

Integer.compare(x, y)String.compareTo(s)와 같은 헬퍼 메서드들은 이미 이 계약을 따르므로 compare 메서드를 구현하는 데 매우 유용합니다.

요약하자면, Comparator는 유연하고 재사용 가능한 정렬 로직을 제공하는 자바 컬렉션 프레임워크의 필수 도구입니다. 특히 자바 8에서 도입된 유창하고 가독성 좋은 헬퍼 메서드 덕분에 간단하고 복잡한 정렬 규칙을 즉석에서 매우 쉽게 정의할 수 있게 되었습니다.

references